﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Common.Utilities;
using PdfLib;

namespace Workflows.Components.TextMining.EnumerationObjects
{
    /// <summary>
    /// 
    /// </summary>
    public class EsnapExtractor
    {
        public event ExtractionError OnExtractionError;

        #region work
        /// <summary>
        /// 
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="rule"></param>
        /// <param name="pageNumbers"></param>
        /// <returns></returns>
        public Dictionary<int, DataTable> GetTableFromFile(string filePath, ExtractionRule rule, List<int> pageNumbers)
        {
            try
            {
                Dictionary<int, DataTable> tableDataByPage = new Dictionary<int, DataTable>();
                List<int> applyOrders = new List<int>();
                foreach (int applyOrder in rule.ColHeaderOptions.Keys)
                {
                    applyOrders.Add(applyOrder);
                }
                applyOrders.Sort();
                foreach (int applyOrder in applyOrders)
                {
                    List<PdfTableColumnHeaderMapping> pdfTableFieldMappings =
                        rule.ColHeaderOptions[applyOrder];
                    List<string> colHeaders = new List<string>();
                    foreach (PdfTableColumnHeaderMapping pdfTableFieldMapping in pdfTableFieldMappings)
                    {
                        if (!string.IsNullOrEmpty(pdfTableFieldMapping.FieldNameInPdf) && (!colHeaders.Contains(pdfTableFieldMapping.FieldNameInPdf)))
                        {
                            colHeaders.Add(pdfTableFieldMapping.FieldNameInPdf);
                        }
                    }

                    try
                    {
                        if (pageNumbers == null || pageNumbers.Count == 0)
                        {
                            throw new Exception("Unable to find pages that contain table in file " + filePath);
                        }
                        foreach (int pageNum in pageNumbers)
                        {
                            try
                            {
                                SizeF pageSize = PdfPropertyReader.GetPageSize(filePath, pageNum);
                                List<PdfTextToken> allTextTokens =
                                    PdfTextReader.ReadPageContentAsTokens(filePath, pageNum);
                                List<PdfTextLine> allPdfLines = PdfTextReader.CreateLines(allTextTokens);
                                Dictionary<string, RectangleF> fieldRects =
                                    this.DetectColumnHeaderLayouts(filePath, pageNum, allPdfLines, allTextTokens,
                                                                   colHeaders, pageSize);
                                // this.GetRectangles(filePath, pageNum, allTextTokens, colHeaders, pageSize);
                                if (fieldRects == null || fieldRects.Count == 0)
                                {
                                    throw new Exception("Unable to determine rectangles based on column names");
                                }
                                DataTable dtContent = this.GetTableData(allTextTokens, fieldRects);
                                if (dtContent == null || dtContent.Rows.Count == 0)
                                {
                                    throw new Exception("Table is empty.");
                                }
                                // check table format twisted 
                                // hard-coded
                                if (dtContent.Columns.Contains("SSN"))
                                {
                                    int ssnColIdx = dtContent.Columns["SSN"].Ordinal;
                                    int ssnMissPlacedCount = 0;
                                    Regex ssnRegex = new Regex("\\d\\d\\d\\d", RegexOptions.IgnoreCase);
                                    Regex dateRegex = new Regex("\\d/\\d\\d\\d\\d", RegexOptions.IgnoreCase);
                                    for (int i = 0; i < dtContent.Rows.Count; i++)
                                    {
                                        for (int k = 0; k < dtContent.Columns.Count; k++)
                                        {
                                            if (dtContent.Rows[i][k] != null && (!string.IsNullOrEmpty(dtContent.Rows[i][k].ToString())))
                                            {
                                                string fieldValue = dtContent.Rows[i][k].ToString();
                                                Match ssnMatch = ssnRegex.Match(fieldValue);
                                                Match dateMatch = dateRegex.Match(fieldValue);
                                                if (ssnMatch.Success && !dateMatch.Success)
                                                {
                                                    if (k != ssnColIdx)
                                                    {
                                                        ssnMissPlacedCount += 1;
                                                    }
                                                    else
                                                    {
                                                        ssnMissPlacedCount -= 1;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    if (ssnMissPlacedCount > 0)
                                    {
                                        string twistedColHeader =
                                            this.GetTwistedColumnHeader(allTextTokens, colHeaders) +
                                            " -twisted";
                                        colHeaders.Add(twistedColHeader);
                                        fieldRects =
                                            this.GetRectangles(filePath, pageNum, allTextTokens, colHeaders, pageSize);
                                        if (fieldRects == null || fieldRects.Count == 0)
                                        {
                                            throw new Exception("Unable to determine rectangles based on column names");
                                        }
                                        dtContent = this.GetTableData(allTextTokens, fieldRects);
                                        if (dtContent == null || dtContent.Rows.Count == 0)
                                        {
                                            throw new Exception("Table is empty.");
                                        }
                                        DataTable dtClone = dtContent.Clone();
                                        dtClone.Columns.Remove(twistedColHeader);
                                        List<object> itemArray = new List<object>();
                                        for (int rowNum = 0; rowNum < dtContent.Rows.Count; rowNum++)
                                        {
                                            for (int colNum = 0; colNum < dtContent.Columns.Count; colNum++)
                                            {
                                                object itemValue = dtContent.Rows[rowNum][colNum];
                                                if (itemValue != null && (!string.IsNullOrEmpty(itemValue.ToString())))
                                                {
                                                    bool skip = false;
                                                    string itemValueStr = itemValue.ToString().ToLower();
                                                    if (itemValueStr.Contains("%") || itemValueStr.Contains("effort"))
                                                        skip = true;
                                                    if (!skip)
                                                        itemArray.Add(itemValue);
                                                }
                                                else
                                                    itemArray.Add(string.Empty);
                                            }
                                        }
                                        int totalCellAdded = 0;
                                        while (totalCellAdded + dtClone.Columns.Count <= itemArray.Count)
                                        {
                                            DataRow drClone = dtClone.NewRow();
                                            for (int i = 0; i < dtClone.Columns.Count; i++)
                                            {
                                                drClone[i] = itemArray[totalCellAdded + i];
                                            }
                                            totalCellAdded += dtClone.Columns.Count;
                                            dtClone.Rows.Add(drClone);
                                        }
                                        dtContent = dtClone;
                                    }
                                }

                                if (tableDataByPage.ContainsKey(pageNum))
                                {
                                    DataTable dtExistingTableData = tableDataByPage[pageNum];
                                    DataRow[] drNonNullExisting =
                                        dtExistingTableData.Select("len(" + dtExistingTableData.Columns[0].ColumnName +
                                                                   ")>0");
                                    DataRow[] drNonNullCurrent =
                                        dtContent.Select("len(" + dtContent.Columns[0].ColumnName + ")>0");

                                    if (drNonNullCurrent.Length > drNonNullExisting.Length)
                                    {
                                        tableDataByPage[pageNum] = dtExistingTableData;
                                    }
                                }
                                else
                                {
                                    tableDataByPage.Add(pageNum, dtContent);
                                }
                            }
                            catch (Exception ex1)
                            {
                                if(this.OnExtractionError !=null)
                                {
                                    this.OnExtractionError(ExtractionStatus.ExtractionFailed, ex1.ToString());
                                }
                                GC.Collect();
                                GC.WaitForPendingFinalizers();
                            }
                        }
                    }
                    catch (Exception ex0)
                    {
                        if (this.OnExtractionError != null)
                        {
                            this.OnExtractionError(ExtractionStatus.ExtractionFailed, ex0.ToString());
                        }
                    }
                }

                return tableDataByPage;
            }
            catch (Exception ex)
            {
                if (this.OnExtractionError != null)
                {
                    this.OnExtractionError(ExtractionStatus.ExtractionFailed, ex.ToString());
                }
                return null;
            }
        }

        private string GetTwistedColumnHeader(List<PdfTextToken> allTextTokens, List<string> colHeaders)
        {
            string twistedColHeader = string.Empty;
            allTextTokens.Sort();
            List<string> colHeaderClone = new List<string>();
            colHeaderClone.AddRange(colHeaders);

            for (int i = 0; i < allTextTokens.Count; i++)
            {
                if (colHeaderClone.Count > 0)
                {
                    for (int k = 0; k < colHeaderClone.Count; k++)
                    {
                        string colName = colHeaderClone[k];
                        string[] colHeaderWords = colName.Split(new char[] { ' ' });
                        string colHeaderFirstWord = colHeaderWords[0];
                        if (!string.IsNullOrEmpty(allTextTokens[i].Text) && !string.IsNullOrEmpty(colHeaderFirstWord) &&
                            colHeaderFirstWord.Length > 0)
                        {
                            if (allTextTokens[i].Text.ToLower().Trim().StartsWith(colHeaderFirstWord.ToLower().Trim()))
                            {
                                colHeaderClone.Remove(colName);
                                break;
                            }
                        }
                    }
                }
                else
                {
                    twistedColHeader = allTextTokens[i].Text;
                    break;
                }
            }
            return twistedColHeader;
        }

        private Dictionary<string, string> CreateFieldMappings(Dictionary<int, List<PdfTableColumnHeaderMapping>> colHeaderOps)
        {
            Dictionary<string, string> srcField_TgtField = new Dictionary<string, string>();
            foreach (int order in colHeaderOps.Keys)
            {
                List<PdfTableColumnHeaderMapping> colHeaders = colHeaderOps[order];
                colHeaders.Sort();
                foreach (PdfTableColumnHeaderMapping colHeader in colHeaders)
                {
                    if (!srcField_TgtField.ContainsKey(colHeader.FieldNameInPdf))
                    {
                        srcField_TgtField.Add(colHeader.FieldNameInPdf, colHeader.FieldNameInDB);
                    }
                }
            }

            return srcField_TgtField;
        }
        #endregion

        #region detect table existance in pages
        private List<int> GetPagesThatContainTable(string filePath, string tblHeader, string tblFooter, List<string> colHeaders)
        {
            List<int> pageNumbers = new List<int>();
            int totalPage = PdfPropertyReader.GetPageCount(filePath);
            try
            {
                PdfPosition headerPos = PdfDocumentStringPatternFinder.SeekPattern(filePath, tblHeader);
                while (headerPos.PageNumber < totalPage)
                {
                    if (!string.IsNullOrEmpty(tblFooter))
                    {
                        try
                        {
                            PdfPosition footerPos =
                                PdfDocumentStringPatternFinder.SeekFromPage(filePath, headerPos.PageNumber, tblFooter);
                            if (footerPos.PageNumber == headerPos.PageNumber)
                            {
                                pageNumbers.Add(headerPos.PageNumber);
                            }
                        }
                        catch (Exception ex1)
                        {
                            if (this.OnExtractionError != null)
                            {
                                this.OnExtractionError(ExtractionStatus.ExtractionFailed, ex1.ToString());
                            }
                            pageNumbers.Add(headerPos.PageNumber);
                        }
                    }
                    else
                    {
                        pageNumbers.Add(headerPos.PageNumber);
                    }

                    headerPos.PageNumber = headerPos.PageNumber + 1;
                    try
                    {
                        headerPos =
                            PdfDocumentStringPatternFinder.SeekFromPage(filePath, headerPos.PageNumber, tblHeader);
                    }
                    catch
                    {
                        //if (this.WorkerErredEvent != null)
                        //{
                        //    this.WorkerErredEvent(this._TicketID, this.GetType(), this._Instruction, ex2.ToString());
                        //}
                        break;
                    }
                }
                if (headerPos.PageNumber == totalPage)
                {
                    pageNumbers.Add(headerPos.PageNumber);
                }
            }
            catch (Exception ex)
            {
                if (this.OnExtractionError != null)
                {
                    this.OnExtractionError(ExtractionStatus.ExtractionFailed, ex.ToString());
                }
                GC.Collect();
            }

            if (pageNumbers.Count > 0)
            {
                List<int> pageNumValidated = new List<int>();
                foreach (int pageNum in pageNumbers)
                {
                    string pageContent = PdfTextReader.ReadPageContent(filePath, pageNum);
                    if (CheckColumnHeaderExistance(pageContent, colHeaders))
                    {
                        pageNumValidated.Add(pageNum);
                    }
                }
                return pageNumValidated;
            }
            else
                return null;
        }

        private bool CheckColumnHeaderExistance(string pageContent, List<string> colHeaders)
        {
            List<string> headers = new List<string>();
            foreach (string colHeader in colHeaders)
            {
                headers.Add(colHeader.ToLower());
            }
            StringBuilder headerBuffer = new StringBuilder();
            foreach (string colHeader in colHeaders)
            {
                headerBuffer.Append(colHeader + " ");
            }
            string concatedHeaderString = headerBuffer.ToString();
            int distance = StringUtil.LongestCommonSubsequence(pageContent, concatedHeaderString);
            if (distance > concatedHeaderString.Length / 2)
                return true;
            else
                return false;
        }
        #endregion

        #region detect column layout using header position
        private Dictionary<string, RectangleF> GetRectangles(string filePath, int pageNum, List<PdfTextToken> allTextTokens, List<string> colHeaders, SizeF pageSize)
        {
            Dictionary<string, PointF> colHeaderStartPositions = new Dictionary<string, PointF>();
            List<string> colHeaderNotFound = new List<string>();
            colHeaderNotFound.AddRange(colHeaders);
            float yFrom = pageSize.Height;
            float yTo = 0;
            allTextTokens.Sort();

            List<string> colHeaderClone = new List<string>();
            colHeaderClone.AddRange(colHeaders);
            for (int i = 0; i < allTextTokens.Count; i++)
            {
                if (colHeaderClone.Count > 0)
                {
                    string colName = colHeaderClone[0];
                    string[] colHeaderWords = colName.Split(new char[] { ' ' });
                    string colHeaderFirstWord = colHeaderWords[0];
                    if (!string.IsNullOrEmpty(allTextTokens[i].Text) && !string.IsNullOrEmpty(colHeaderFirstWord) &&
                        colHeaderFirstWord.Length > 0)
                    {
                        if (allTextTokens[i].Text.ToLower().Trim().StartsWith(colHeaderFirstWord.ToLower().Trim()))
                        {
                            PointF pos = new PointF(allTextTokens[i].TextRect.X, allTextTokens[i].TextRect.Y);
                            if (colHeaderStartPositions.ContainsKey(colName))
                            {
                                // colHeaderStartPositions[colName] = pos;
                            }
                            else
                            {
                                colHeaderStartPositions.Add(colName, pos);
                                colHeaderNotFound.Remove(colName);
                                if (yFrom > pos.Y)
                                {
                                    yFrom = pos.Y;
                                }
                                if (yTo < pos.Y)
                                {
                                    yTo = pos.Y;
                                }
                            }

                            colHeaderClone.Remove(colName);
                        }
                    }
                }
            }

            if (colHeaderNotFound.Count == 0)
            {
                Dictionary<string, RectangleF> rectanglesForTableContents = new Dictionary<string, RectangleF>();
                List<float> vLinePositions = PdfPathDetector.GetVerticalLines(filePath, pageNum, yFrom, yTo);
                if (vLinePositions != null && vLinePositions.Count > 0 && vLinePositions.Count >= colHeaders.Count - 1)
                {
                    vLinePositions.Sort();
                    foreach (string colName in colHeaderStartPositions.Keys)
                    {
                        PointF headerPos = colHeaderStartPositions[colName];
                        float xFrom = 0;
                        float xTo = pageSize.Width;
                        for (int k = 0; k < vLinePositions.Count; k++)
                        {
                            if (vLinePositions[k] < headerPos.X + 4)
                            {
                                xFrom = vLinePositions[k];
                            }
                            else if (vLinePositions[k] > headerPos.X + 12)
                            {
                                xTo = vLinePositions[k];
                                break;
                            }
                        }
                        RectangleF colRect = new RectangleF(xFrom, yTo, xTo - xFrom, pageSize.Height - yTo);
                        rectanglesForTableContents.Add(colName, colRect);
                    }
                    return rectanglesForTableContents;
                }
                else
                {
                    float lastX = pageSize.Width;
                    for (int i = colHeaders.Count - 1; i >= 0; i--)
                    {
                        string colName = colHeaders[i];
                        PointF colPos = colHeaderStartPositions[colName];
                        RectangleF colRect = new RectangleF(colPos.X, yTo, lastX - colPos.X, pageSize.Height - yTo);
                        rectanglesForTableContents.Add(colName, colRect);
                        lastX = colPos.X;
                    }
                    return rectanglesForTableContents;
                }
            }
            else
                return null;
        }

        private Dictionary<string, RectangleF> DetectColumnHeaderLayouts(string filePath, int pageNum, List<PdfTextLine> allPdfLines, List<PdfTextToken> allTextTokens, List<string> colHeaders, SizeF pageSize)
        {
            allTextTokens.Sort();
            allPdfLines.Sort();
            List<string> firstWordsInColHeaders = new List<string>();
            foreach (string colHeader in colHeaders)
            {
                string[] wordsInColHeader = colHeader.Split(new char[] { ' ' });
                string firstWordInColHeader = wordsInColHeader[0].ToLower();
                firstWordsInColHeaders.Add(firstWordInColHeader);
            }

            // determine y range for column header
            float yFrom = pageSize.Height;
            float yTo = 0;
            int columnHeaderLineIndex = -1;
            for (int i = 0; i < allPdfLines.Count; i++)
            {
                if (allPdfLines[i].LineContent.ToLower().StartsWith(firstWordsInColHeaders[0]))
                {
                    string bloatedLineText = this.GetBloatedLineText(allPdfLines, i, ref yFrom, ref yTo).ToLower();
                    int colHeaderFound = 0;
                    foreach (string firstWord in firstWordsInColHeaders)
                    {
                        if (bloatedLineText.Contains(firstWord))
                        {
                            colHeaderFound++;
                        }
                    }
                    if (colHeaderFound == colHeaders.Count)
                    {
                        columnHeaderLineIndex = i;
                        break;
                    }
                }
            }

            // determine column header x ranges
            if (columnHeaderLineIndex >= 0)
            {
                Dictionary<string, Range<float>> colRanges = new Dictionary<string, Range<float>>();
                float tmpYFrom = pageSize.Height;
                float tmpYTo = 0;
                foreach (PdfTextToken txtToken in allTextTokens)
                {
                    if (txtToken.Position.Y >= yFrom && txtToken.Position.Y <= yTo)
                    {
                        for (int i = 0; i < colHeaders.Count; i++)
                        {
                            string firstWord = firstWordsInColHeaders[i];
                            if (txtToken.Text.ToLower().StartsWith(firstWord.ToLower()))
                            {
                                if (tmpYFrom > txtToken.Position.Y)
                                    tmpYFrom = txtToken.Position.Y;
                                if (tmpYTo < txtToken.Position.Y)
                                    tmpYTo = txtToken.Position.Y;
                                if (!colRanges.ContainsKey(colHeaders[i]))
                                {
                                    Range<float> colRange = new Range<float>();
                                    colRange.FromValue = txtToken.Position.X;
                                    colRange.ToValue = txtToken.Position.X + txtToken.TextRect.Width;
                                    colRanges.Add(colHeaders[i], colRange);
                                }
                            }
                        }
                    }
                }
                yFrom = Math.Max(tmpYFrom, yFrom);
                yTo = Math.Min(tmpYTo, yTo);

                foreach (PdfTextToken txtToken in allTextTokens)
                {
                    if (txtToken.Position.Y > yFrom && txtToken.Position.Y < Math.Min(yTo, yFrom + 40))
                    {
                        Range<float> txtTokenRange = new Range<float>();
                        txtTokenRange.FromValue = txtToken.Position.X;
                        txtTokenRange.ToValue = txtToken.Position.X + txtToken.TextRect.Width;
                        foreach (string colHeader in colHeaders)
                        {
                            if (colRanges.ContainsKey(colHeader))
                            {
                                Range<float> colHeaderRange = colRanges[colHeader];
                                if (colHeaderRange.OverlapWith(txtTokenRange))
                                {
                                    colHeaderRange = colHeaderRange.Union(txtTokenRange);
                                    colRanges[colHeader] = colHeaderRange;
                                }
                            }
                        }
                    }
                }

                // determine col ranges in x 
                Dictionary<string, RectangleF> rectanglesForTableContents = new Dictionary<string, RectangleF>();
                List<float> vLinePositions = PdfPathDetector.GetVerticalLines(filePath, pageNum, yFrom, yTo);
                if (vLinePositions != null && vLinePositions.Count > 0 && vLinePositions.Count >= colHeaders.Count - 1)
                {
                    vLinePositions.Sort();
                    foreach (string colName in colRanges.Keys)
                    {
                        Range<float> headerRange = colRanges[colName];
                        float xFrom = 0;
                        float xTo = pageSize.Width;
                        for (int k = 0; k < vLinePositions.Count; k++)
                        {
                            if (vLinePositions[k] < headerRange.FromValue + 4)
                            {
                                xFrom = vLinePositions[k];
                            }
                            else if (vLinePositions[k] > headerRange.FromValue + 12)
                            {
                                xTo = vLinePositions[k];
                                break;
                            }
                        }
                        RectangleF colRect = new RectangleF(xFrom, yTo, xTo - xFrom, pageSize.Height - yTo);
                        rectanglesForTableContents.Add(colName, colRect);
                    }
                    return rectanglesForTableContents;
                }
                else
                {
                    float lastX = pageSize.Width;
                    for (int i = colHeaders.Count - 1; i >= 0; i--)
                    {
                        string colName = colHeaders[i];
                        Range<float> colRange = colRanges[colName];
                        RectangleF colRect = new RectangleF(colRange.FromValue, yTo, lastX - colRange.FromValue, pageSize.Height - yTo);
                        rectanglesForTableContents.Add(colName, colRect);
                        lastX = colRange.FromValue;
                    }
                    return rectanglesForTableContents;
                }
            }
            else
                return null;
        }

        private string GetBloatedLineText(List<PdfTextLine> allPdfLines, int currentIndex, ref float yFrom, ref float yTo)
        {
            int lineFrom = Math.Max(0, currentIndex - 2);
            yFrom = allPdfLines[lineFrom].Rect.Y;
            int lineTo = Math.Min(allPdfLines.Count - 1, currentIndex + 2);
            yTo = allPdfLines[lineTo].Rect.Y + allPdfLines[lineTo].Rect.Height;
            StringBuilder buffer = new StringBuilder();
            for (int i = lineFrom; i <= lineTo; i++)
            {
                buffer.Append(allPdfLines[i].LineContent + Environment.NewLine);
            }
            return buffer.ToString();
        }
        #endregion

        #region extract table data
        private DataTable GetTableData(List<PdfTextToken> allTextTokens, Dictionary<string, RectangleF> colHeaderLayout)
        {
            List<PdfTextLine> pdfLines = this.CreateLines(allTextTokens);
            pdfLines = this.MergeLines(pdfLines, 10);
            DataTable dtData = this.ReadTableContent(pdfLines, allTextTokens, colHeaderLayout);
            return dtData;
        }

        #endregion

        #region read table content
        private List<PdfTextLine> CreateLines(List<PdfTextToken> txtTokens)
        {
            txtTokens.Sort();
            PdfTextLine lastLine = new PdfTextLine();
            List<PdfTextLine> lines = new List<PdfTextLine>();
            foreach (PdfTextToken txtToken in txtTokens)
            {
                if (lastLine.Rect.IsEmpty)
                {
                    lastLine = new PdfTextLine();
                    lastLine.Rect = txtToken.TextRect;
                }
                bool sameLine = false;
                if (lastLine.Rect.IntersectsWith(txtToken.TextRect))
                {
                    sameLine = true;
                    //if (lastLine.Rect.Y + lastLine.Rect.Height > txtToken.TextRect.Y && lastLine.Rect.Y < txtToken.TextRect.Y + txtToken.TextRect.Height)
                    //    sameLine = true;
                }
                else
                    sameLine = false;
                if (sameLine)
                {
                    lastLine.Rect = RectangleF.Union(lastLine.Rect, txtToken.TextRect);
                }
                else
                {
                    lines.Add(lastLine);
                    lastLine = new PdfTextLine();
                    lastLine.Rect = txtToken.TextRect;
                }
            }
            if (!PdfTextLine.IsEmpty(lastLine))
                lines.Add(lastLine);
            lines.Sort();
            return lines;
        }

        private List<PdfTextLine> MergeLines(List<PdfTextLine> lines, float minRowSpace)
        {
            List<PdfTextLine> mergedLines = new List<PdfTextLine>();
            if (lines != null && lines.Count > 0)
            {
                PdfTextLine lastLine = PdfTextLine.Empty;
                for (int i = 0; i < lines.Count; i++)
                {
                    if (PdfTextLine.IsEmpty(lastLine))
                    {
                        lastLine = lines[i];
                    }
                    else
                    {
                        RectangleF lastRect = lastLine.Rect;
                        RectangleF currentRect = lines[i].Rect;
                        //lastRect.Inflate(fontSize, fontSize);
                        //currentRect.Inflate(fontSize, fontSize);
                        if (currentRect.Y < lastRect.Y + minRowSpace)
                        {
                            lastRect = RectangleF.Union(lastLine.Rect, lines[i].Rect);
                            lastLine.Rect = lastRect;
                        }
                        else
                        {
                            mergedLines.Add(lastLine);
                            lastLine = lines[i];
                        }
                    }
                }
                if (!PdfTextLine.IsEmpty(lastLine))
                {
                    mergedLines.Add(lastLine);
                }
            }
            return mergedLines;
        }

        private DataTable ReadTableContent(List<PdfTextLine> lines, List<PdfTextToken> allTextTokens, Dictionary<string, RectangleF> tableFieldRects)
        {
            float readFromY = 0;
            float readToY = 100000;
            List<SortableRectangle> sortableRectList = new List<SortableRectangle>();
            foreach (RectangleF fieldRect in tableFieldRects.Values)
            {
                if (fieldRect.Y > readFromY)
                    readFromY = fieldRect.Y;
                if (fieldRect.Y + fieldRect.Height < readToY)
                    readToY = fieldRect.Y + fieldRect.Height;

                SortableRectangle sortableRect = new SortableRectangle(fieldRect);
                sortableRectList.Add(sortableRect);
            }
            sortableRectList.Sort();
            DataTable dtContent = new DataTable();
            foreach (SortableRectangle sortableRect in sortableRectList)
            {
                string colName = string.Empty;
                foreach (string key in tableFieldRects.Keys)
                {
                    RectangleF fieldRect = tableFieldRects[key];
                    if (fieldRect.X == sortableRect.Rect.X && fieldRect.Width == sortableRect.Rect.Width)
                    {
                        colName = key;
                        break;
                    }
                }
                if (!string.IsNullOrEmpty(colName))
                {
                    dtContent.Columns.Add(colName, typeof(string));
                }
            }

            lines.Sort();

            for (int i = 0; i < lines.Count; i++)
            {
                PdfTextLine line = lines[i];
                if (line.Rect.Y >= readFromY && line.Rect.Y <= readToY)
                {
                    DataRow drContent = dtContent.NewRow();
                    foreach (PdfTextToken txtToken in allTextTokens)
                    {
                        if (txtToken.TextRect.IntersectsWith(line.Rect))
                        {
                            string colName = this.GetCorrespondingColumn(txtToken.TextRect, tableFieldRects);
                            if (!string.IsNullOrEmpty(colName))
                            {
                                if (drContent[colName] != null)
                                {
                                    string fieldValue = drContent[colName].ToString();
                                    fieldValue += " " + txtToken.Text;
                                    drContent[colName] = fieldValue;
                                }
                                else
                                    drContent[colName] = txtToken.Text;
                            }
                        }
                    }
                    dtContent.Rows.Add(drContent);
                }
            }
            return dtContent;
        }

        private string GetCorrespondingColumn(RectangleF txtRect, Dictionary<string, RectangleF> fieldRects)
        {
            foreach (string colName in fieldRects.Keys)
            {
                RectangleF fieldRect = fieldRects[colName];
                if (fieldRect.X <= txtRect.X + 4 && fieldRect.X + fieldRect.Width + 4 > txtRect.X + txtRect.Width)
                {
                    return colName;
                }
            }
            return string.Empty;
        }
        #endregion
    }
}
